package Leetcode.网格图;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: kirito
 * @Date: 2024/5/2 12:04
 * @Description:
 * 太平洋大西洋水流问题
 * 中等
 * 相关标签
 * 相关企业
 * 有一个 m × n 的矩形岛屿，与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界，而 “大西洋” 处于大陆的右边界和下边界。
 *
 * 这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights ， heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度 。
 *
 * 岛上雨水较多，如果相邻单元格的高度 小于或等于 当前单元格的高度，雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。
 *
 * 返回网格坐标 result 的 2D 列表 ，其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋 。
 *
 *
 *
 * 示例 1：
 *
 *
 *
 * 输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
 * 输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
 * 示例 2：
 *
 * 输入: heights = [[2,1],[1,2]]
 * 输出: [[0,0],[0,1],[1,0],[1,1]]
 *
 *
 * 提示：
 *
 * m == heights.length
 * n == heights[r].length
 * 1 <= m, n <= 200
 * 0 <= heights[r][c] <= 105
 */

public class pacificAtlantic {


    /*
    简单描述一下题目，雨水只能从高的地方流向低的地方，求二维表格中哪些坐标的雨水既可以流入太平洋又可以流入大西洋。

    雨水的流动方向是从高到低，每个单元格上的雨水只能流到高度小于等于当前单元格的相邻单元格。
    从一个单元格开始，通过搜索的方法模拟雨水的流动，则可以判断雨水是否可以从该单元格流向海洋。

    如果直接以每个单元格作为起点模拟雨水的流动，则会重复遍历每个单元格，导致时间复杂度过高。
    为了降低时间复杂度，可以从矩阵的边界开始反向搜索寻找雨水流向边界的单元格，反向搜索时，
    每次只能移动到高度相同或更大的单元格。

    由于矩阵的左边界和上边界是太平洋，矩阵的右边界和下边界是大西洋，
    因此从矩阵的左边界和上边界开始反向搜索即可找到雨水流向太平洋的单元格，
    从矩阵的右边界和下边界开始反向搜索即可找到雨水流向大西洋的单元格。

    可以使用深度优先搜索实现反向搜索，搜索过程中需要记录每个单元格是否可以从太平洋反向到达以及是否可以从大西洋反向到达。
    反向搜索结束之后，遍历每个网格，如果一个网格既可以从太平洋反向到达也可以从大西洋反向到达
    ，则该网格满足太平洋和大西洋都可以到达，将该网格添加到答案中。

     */
    static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 定义四个方向向量，用于寻址
    int[][] heights; // 高度数组，用于存储每个单元格的高度
    int m, n; // 行数和列数

    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        this.heights = heights; // 初始化高度数组
        this.m = heights.length; // 初始化解的行数
        this.n = heights[0].length; // 初始化解的列数
        boolean[][] pacific = new boolean[m][n]; // 创建一个二维布尔数组，用于标记太平洋海洋的水位
        boolean[][] atlantic = new boolean[m][n]; // 创建一个二维布尔数组，用于标记大西洋海洋的水位
        // 遍历每一行，从左到右遍历，将太平洋的水位标记为true
        for (int i = 0; i < m; i++) {
            dfs(i, 0, pacific);
        }
        // 遍历第一列，从上到下遍历，将太平洋的水位标记为true
        for (int j = 1; j < n; j++) {
            dfs(0, j, pacific);
        }
        // 遍历每一行，从右到左遍历，将大西洋的水位标记为true
        for (int i = 0; i < m; i++) {
            dfs(i, n - 1, atlantic);
        }
        // 遍历最后一列，从下到上遍历，将大西洋的水位标记为true
        for (int j = 0; j < n - 1; j++) {
            dfs(m - 1, j, atlantic);
        }
        List<List<Integer>> result = new ArrayList<List<Integer>>(); // 创建一个结果列表
        // 遍历整个矩阵，如果一个单元格同时被太平洋和大西洋标记，则将其坐标添加到结果列表中
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (pacific[i][j] && atlantic[i][j]) {
                    List<Integer> cell = new ArrayList<Integer>(); // 创建一个单元格列表
                    cell.add(i); // 添加行坐标
                    cell.add(j); // 添加列坐标
                    result.add(cell); // 将单元格添加到结果列表中
                }
            }
        }
        return result; // 返回结果列表
    }

    public void dfs(int row, int col, boolean[][] ocean) {
        // 如果该单元格已经被访问过，则直接返回
        if (ocean[row][col]) {
            return;
        }
        // 标记该单元格为已访问
        ocean[row][col] = true;
        // 遍历四个方向
        for (int[] dir : dirs) {
            int newRow = row + dir[0], newCol = col + dir[1]; // 计算新坐标
            // 检查新坐标是否在矩阵范围内
            if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n) {
                // 如果新坐标的高度不小于当前坐标的高度，则递归访问新坐标
                if (heights[newRow][newCol] >= heights[row][col]) {
                    dfs(newRow, newCol, ocean);
                }
            }
        }
    }


}
